using System;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices.ComTypes;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using System.Drawing;
using Microsoft.Win32;
using System.IO;

namespace Tsanie.ShellExtension {

    #region - Attribute -

    /// <summary>    
    /// This class attribute is applied to a Thumbnail Provider to specify registration parameters
    /// and aesthetic attributes.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
    public sealed class ThumbnailProviderAttribute : Attribute {
        /// <summary>
        /// Creates a new instance of the attribute.
        /// </summary>
        /// <param name="name">Name of the provider</param>
        /// <param name="extensions">Semi-colon-separated list of extensions supported by this provider.</param>
        public ThumbnailProviderAttribute(string name, string extensions) {
            if (name == null)
                throw new ArgumentNullException("name");
            if (extensions == null)
                throw new ArgumentNullException("extensions");
            Name = name;
            Extensions = extensions;
            DisableProcessIsolation = false;
            ThumbnailCutoff = ThumbnailCutoffSize.Square20;
            TypeOverlay = null;
            ThumbnailAdornment = ThumbnailAdornment.Default;
        }

        /// <summary>
        /// Gets the name of the provider
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the semi-colon-separated list of extensions supported by the provider.
        /// </summary>
        public string Extensions { get; private set; }

        // optional parameters below.

        /// <summary>
        /// Opts-out of running within the surrogate process DllHost.exe.
        /// This will reduce robustness and security.
        /// This value should be true if the provider does not implement <typeparamref name="IThumbnailFromStream"/>.
        /// </summary>
        // Note: The msdn documentation and property name are contradicting.
        // http://msdn.microsoft.com/en-us/library/cc144118(VS.85).aspx
        public bool DisableProcessIsolation { get; set; } // If true: Makes it run IN PROCESS.
        /// <summary>
        /// Below this size thumbnail images will not be generated - file icons will be used instead.
        /// </summary>
        public ThumbnailCutoffSize ThumbnailCutoff { get; set; }
        /// <summary>
        /// A resource reference string pointing to the icon to be used as an overlay on the bottom right of the thumbnail.
        /// ex. ISVComponent.dll@,-155
        /// ex. C:\Windows\System32\SampleIcon.ico
        /// If an empty string is provided, no overlay will be used.
        /// If the property is set to null, the default icon for the associated icon will be used as an overlay.
        /// </summary>
        public string TypeOverlay { get; set; }
        /// <summary>
        /// Specifies the <typeparamref name="ThumbnailAdornment"/> for the thumbnail.
        /// <remarks>
        /// Only 32bpp bitmaps support adornments. 
        /// While 24bpp bitmaps will be displayed, their adornments will not.
        /// If an adornment is specified by the file-type's associated application, 
        /// the applications adornment will override the value specified in this registration.</remarks>
        /// </summary>
        public ThumbnailAdornment ThumbnailAdornment { get; set; }

    }

    /// <summary>
    /// Defines the minimum thumbnail size for which thumbnails will be generated.
    /// </summary>
    public enum ThumbnailCutoffSize {
        /// <summary>
        /// Default size of 20x20
        /// </summary>
        Square20 = -1, //For 20x20, you do not add any key in the registry
        /// <summary>
        /// Size of 32x32
        /// </summary>
        Square32 = 0,
        /// <summary>
        /// Size of 16x16
        /// </summary>
        Square16 = 1,
        /// <summary>
        /// Size of 48x48
        /// </summary>
        Square48 = 2,
        /// <summary>
        /// Size of 16x16. An alternative to Square16.
        /// </summary>
        Square16B = 3
    }

    /// <summary>
    /// Adornment applied to thumbnails.
    /// </summary>
    public enum ThumbnailAdornment {
        /// <summary>
        /// This will use the associated application's default icon as the adornment.
        /// </summary>
        Default = -1, // Default behaviour for no value added in registry
        /// <summary>
        /// No adornment
        /// </summary>
        None = 0,
        /// <summary>
        /// Drop shadow adornment
        /// </summary>
        DropShadow = 1,
        /// <summary>
        /// Photo border adornment
        /// </summary>
        PhotoBorder = 2,
        /// <summary>
        /// Video sprocket adornment
        /// </summary>
        VideoSprockets = 3
    }

    #endregion

    #region - Interface -

#if TRACE

    enum STGTY {
        STORAGE = 1,
        STREAM = 2,
        LOCKBYTES = 3,
        PROPERTY = 4
    }

#endif

    public enum AlphaType : uint {
        Unknown = 0,
        Rgb = 1,
        Argb = 2
    }

    /// <summary>
    /// ComVisible interface for native IThumbnailProvider
    /// </summary>
    [ComImport]
    [Guid("e357fccd-a995-4576-b01f-234630154e96")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IThumbnailProvider {
        /// <summary>
        /// Gets a pointer to a bitmap to display as a thumbnail
        /// </summary>
        /// <param name="squareLength"></param>
        /// <param name="bitmapHandle"></param>
        /// <param name="bitmapType"></param>
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
        [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
        void GetThumbnail(uint squareLength, [Out] out IntPtr bitmapHandle, [Out] out AlphaType bitmapType);
    }

    #endregion

    #region - ThumbnailProvider -

    public abstract class ThumbnailProviderBase : IThumbnailProvider, IInitializeWithStream, IDisposable {

        #region - Private -

        private StorageStream _stream;

        #endregion

        #region - Public -

        public StorageStream Stream { get { return _stream; } }
        public abstract Bitmap Thumbnail { get; }
        public virtual AlphaType AlphaType { get { return AlphaType.Rgb; } }

        #endregion

        #region - Implements Interface -

        void IThumbnailProvider.GetThumbnail(uint squareLength, out IntPtr bitmapHandle, out AlphaType bitmapType) {
            bitmapType = this.AlphaType;
            bitmapHandle = this.Thumbnail.GetHbitmap();
        }

        void IInitializeWithStream.Initialize(IStream stream, AccessModes fileMode) {
            _stream = new StorageStream(stream, fileMode != AccessModes.ReadWrite);
#if TRACE
            Common.OutputDebugString("{0} - size: {1}, type: {2}", new object[] {
                _stream.Filename,
                _stream.Length,
                (STGTY)_stream.Type
            });
#endif
        }

        #endregion

        #region - Register/Unregister -

        [ComRegisterFunction]
        private static void Register(Type type) {
            if (type != null && type.IsSubclassOf(typeof(ThumbnailProviderBase))) {
                object[] attributes = type.GetCustomAttributes(typeof(ThumbnailProviderAttribute), true);
                if (attributes != null && attributes.Length == 1) {
                    ThumbnailProviderAttribute attribute = (ThumbnailProviderAttribute)attributes[0];
                    RegisterThumbnailHandler(type.GUID.ToString("B"), attribute);
                    Native.SHChangeNotify(Native.SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
                }
            }
        }
        private static void RegisterThumbnailHandler(string guid, ThumbnailProviderAttribute attribute) {
            // set process isolation
            using (RegistryKey guidKey = Registry.ClassesRoot.OpenSubKey("CLSID\\" + guid, true)) {
                guidKey.SetValue("DisableProcessIsolation", attribute.DisableProcessIsolation ? 1 : 0, RegistryValueKind.DWord);
                using (RegistryKey inproc = guidKey.OpenSubKey("InprocServer32", true)) {
                    inproc.SetValue("ThreadingModel", "Apartment", RegistryValueKind.String);
                }
            }
            // register file as an approved extension
            using (RegistryKey approvedShellExtensions = Registry.LocalMachine.OpenSubKey(
                 @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", true)) {
                approvedShellExtensions.SetValue(guid, attribute.Name, RegistryValueKind.String);
            }
            // register extension with each extension in the list
            string[] extensions = attribute.Extensions.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string extension in extensions) {
                using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(extension)) // Create makes it writable
                using (RegistryKey shellExKey = extensionKey.CreateSubKey("shellex"))
                using (RegistryKey providerKey = shellExKey.CreateSubKey("{E357FCCD-A995-4576-B01F-234630154E96}")) {
                    providerKey.SetValue(null, guid, RegistryValueKind.String);
                    if (attribute.ThumbnailCutoff == ThumbnailCutoffSize.Square20) {
                        extensionKey.DeleteValue("ThumbnailCutoff", false);
                    } else {
                        extensionKey.SetValue("ThumbnailCutoff", (int)attribute.ThumbnailCutoff, RegistryValueKind.DWord);
                    }
                    if (attribute.TypeOverlay != null) {
                        extensionKey.SetValue("TypeOverlay", attribute.TypeOverlay, RegistryValueKind.String);
                    }
                    if (attribute.ThumbnailAdornment == ThumbnailAdornment.Default) {
                        extensionKey.DeleteValue("Treatment", false);
                    } else {
                        extensionKey.SetValue("Treatment", (int)attribute.ThumbnailAdornment, RegistryValueKind.DWord);
                    }
                }
            }
        }

        [ComUnregisterFunction]
        private static void Unregister(Type type) {
            if (type != null && type.IsSubclassOf(typeof(ThumbnailProviderBase))) {
                object[] attributes = type.GetCustomAttributes(typeof(ThumbnailProviderAttribute), true);
                if (attributes != null && attributes.Length == 1) {
                    ThumbnailProviderAttribute attribute = attributes[0] as ThumbnailProviderAttribute;
                    UnregisterThumbnailHandler(type.GUID.ToString("B"), attribute);
                    Native.SHChangeNotify(Native.SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
                }
            }
        }
        private static void UnregisterThumbnailHandler(string guid, ThumbnailProviderAttribute attribute) {
            string[] extensions = attribute.Extensions.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (string extension in extensions) {
                using (RegistryKey extKey = Registry.ClassesRoot.OpenSubKey(extension, true))
                using (RegistryKey shellexKey = extKey.OpenSubKey("shellex", true)) {
                    shellexKey.DeleteSubKey("{E357FCCD-A995-4576-B01F-234630154E96}", false);
                    extKey.DeleteValue("ThumbnailCutoff", false);
                    extKey.DeleteValue("TypeOverlay", false);
                    extKey.DeleteValue("Treatment", false); // Thumbnail adornment
                }
            }
            using (RegistryKey approvedShellExtensions = Registry.LocalMachine.OpenSubKey(
                @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", true)) {
                approvedShellExtensions.DeleteValue(guid, false);
            }
        }

        #endregion

        #region - Dispose -

        /// <summary>
        /// Finalizer for the thumbnail provider.
        /// </summary>
        ~ThumbnailProviderBase() {
            Dispose(false);
        }

        /// <summary>
        /// Disposes the thumbnail provider.
        /// </summary>
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Disploses the thumbnail provider.
        /// </summary>
        /// <param name="disposing"></param>
        protected virtual void Dispose(bool disposing) {
            if (disposing && _stream != null) {
                _stream.Dispose();
            }
        }

        #endregion

    }

    #endregion

}
